package com.zego.zegowawaji_server.callback;

import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
import android.text.TextUtils;

import com.zego.base.bean.CustomZegoUser;
import com.zego.base.http.RequestMgr;
import com.zego.base.utils.AppLogger;
import com.zego.zegoliveroom.ZegoLiveRoom;
import com.zego.zegoliveroom.callback.IZegoCustomCommandCallback;
import com.zego.zegoliveroom.callback.IZegoRoomCallback;
import com.zego.zegoliveroom.entity.ZegoStreamInfo;
import com.zego.zegowawaji_server.listener.IRoomClient;
import com.zego.zegowawaji_server.listener.IStateChangedListener;
import com.zego.zegowawaji_server.manager.CommandSeqManager;
import com.zego.zegowawaji_server.manager.DeviceManager;
import com.zego.zegowawaji_server.utils.Command;
import com.zego.zegowawaji_server.utils.JsonKey;
import com.zego.zegowawaji_server.utils.SynthCmd;

import org.json.JSONException;
import org.json.JSONObject;

import java.util.List;

/**
 * 直播房间回调
 */
public class ZegoRoomCallback implements IZegoRoomCallback {

    private IRoomClient mRoomClient;
    private IStateChangedListener mListener;

    private HandlerThread mMessageThread;
    private Handler mHandler;

    private volatile boolean mCurrentIsIdle = true;     // 当前是否有人正在上机
    private volatile String mCurrentPlayerId = null;

    public ZegoRoomCallback(IRoomClient client, IStateChangedListener listener) {
        mRoomClient = client;
        mListener = listener;

        mMessageThread = new HandlerThread("command-timeout-timer");
        mMessageThread.start();

        mHandler = new Handler(mMessageThread.getLooper(), new HandlerImpl());
    }

    /**
     * 因为登陆抢占原因等被挤出房间
     */
    @Override
    public void onKickOut(int reason, String roomId) {
        AppLogger.getInstance().writeLog("onKickOut, reason: %d, room Id: %s", reason, roomId);
    }

    /**
     * 与 server 断开
     */
    @Override
    public void onDisconnect(int errorCode, String roomId) {
        AppLogger.getInstance().writeLog("onDisconnect, errorCode: %d, room Id: %s", errorCode, roomId);

        if (mListener != null) {
            mListener.onDisconnect();
        }
    }

    /**
     * 中断后重连
     */
    @Override
    public void onReconnect(int errorCode, String roomId) {
        AppLogger.getInstance().writeLog("中断后重连, errorCode: %d, room Id: %s", errorCode, roomId);
    }

    /**
     * 临时中断
     */
    @Override
    public void onTempBroken(int errorCode, String roomId) {
        AppLogger.getInstance().writeLog("临时中断, errorCode: %d, room Id: %s", errorCode, roomId);
    }

    /**
     * 房间流列表更新
     */
    @Override
    public void onStreamUpdated(int type, ZegoStreamInfo[] streamList, String roomId) {
        AppLogger.getInstance().writeLog("onStreamUpdated, type: %d", type);
    }

    /**
     * 更新流的额外信息
     */
    @Override
    public void onStreamExtraInfoUpdated(ZegoStreamInfo[] streamList, String roomId) {
        AppLogger.getInstance().writeLog("onStreamExtraInfoUpdated, streamList count: %d; roomId: %s", streamList.length, roomId);
    }

    /**
     * 处理玩家指令消息: 用户通过该回调接收抓娃娃客户端发送来的指令
     */
    @Override
    public void onRecvCustomCommand(String fromUserId, String fromUserName, String content, String roomId) {
        AppLogger.getInstance().writeLog("onRecvCustomCommand收到操作指令, 操作者: %s; content: %s, roomId: %s", fromUserName, content, roomId);

        JSONObject cmdJson;
        try {
            cmdJson = new JSONObject(content);
        } catch (JSONException e) {
            AppLogger.getInstance().writeLog("onRecvCustomCommand,解析Cmd出错. cmd : %s; from: %s", content, fromUserId);
            return;
        }

        int commandId = cmdJson.optInt(JsonKey.KEY_CMD);
        switch (commandId) {
            // 预约上机申请
            case Command.CMD_APPOINTMENT:
                handleAppointmentCommandInWorkThread(fromUserId, fromUserName, cmdJson);
                break;

            // 取消预约（暂时不用）
            case Command.CMD_CANCEL_APPOINTMENT:
                handleCancelAppointmentInWorkThread(fromUserId, fromUserName, cmdJson);
                break;

            // 确认上机或者放弃玩游戏，仅在正式开始玩之前发送此指令有效，即在收到服务端的 CMD_GAME_READY 指令时，通过该指令告诉服务端开始玩还是放弃
            case Command.CMD_START_OR_ABANDON_GAME:
                handleStartOrAbandonCommandInWorkThread(fromUserId, fromUserName, cmdJson);
                break;

            // 收到回复上机指令
            case Command.CMD_GAME_READY_REPLY:
                handleReadyReplyCommandInWorkThread(fromUserId, fromUserName, cmdJson);
                break;

            // 回复收到游戏结果
            case Command.CMD_GAME_RESULT_REPLY:
                handleGameResultReplyCommandInWorkThread(fromUserId, fromUserName, cmdJson);
                break;

            case Command.CMD_MOVE_LEFT:        // 左移指令
            case Command.CMD_MOVE_RIGHT:       // 右移指令
            case Command.CMD_MOVE_FORWARD:     // 前移指令
            case Command.CMD_MOVE_BACKWARD:    // 后移指令
            case Command.CMD_GRAB:             // 抓指令
                RequestMgr.gameId = cmdJson.optLong(JsonKey.KEY_GAME_ID);
                RequestMgr.cookieVerCode = cmdJson.optString(JsonKey.KEY_USER_AUTH);
                handleOperationCommand(commandId, fromUserId, fromUserName);
                break;
        }
    }

    /**
     * 改变上机状态
     */
    private void setIdle(boolean idle, String callFrom) {
        mCurrentIsIdle = idle;
        AppLogger.getInstance().writeLog("[setIdle]: " + idle + ", callFrom: " + callFrom);
    }

    /**
     * 通知下一位预约用户
     */
    private void notifyNextPlayerIfNeed() {
        mRoomClient.runOnWorkThread(new Runnable() {
            @Override
            public void run() {
                List<CustomZegoUser> queueMembers = mRoomClient.getQueueUser();

                AppLogger.getInstance().writeLog("[通知下一位预约用户], queueMember: " + queueMembers.size() + ", isIdle: " + mCurrentIsIdle);
                if (queueMembers.size() > 0 && mCurrentIsIdle) { // 当前有玩家在排队且没有人上机，则通知队首的玩家准备上机

                    setIdle(false, "notifyNextPlayerIfNeed");

                    final CustomZegoUser zegoUser = queueMembers.get(0);
                    int seq = CommandSeqManager.getInstance().getAndIncreaseSequence();
                    final String beginPlayCmd = SynthCmd.gameReadyCmd(zegoUser.userID, zegoUser.userName, seq);

                    CustomZegoUser[] userArray = {zegoUser};
                    ZegoLiveRoom liveRoom = mRoomClient.getZegoLiveRoom();
                    boolean success = liveRoom.sendCustomCommand(userArray, beginPlayCmd, new IZegoCustomCommandCallback() {
                        @Override
                        public void onSendCustomCommand(int errorCode, String roomId) {
                            AppLogger.getInstance().writeLog("Server通知预约用户准备开始游戏Cmd: %d,  %s", errorCode, beginPlayCmd);

                            // 等待玩家确认/放弃计时
                            Message message = new Message();
                            message.what = HandlerImpl.MSG_WAIT_CONFIRM;

                            Bundle userData = new Bundle();
                            userData.putString("id", zegoUser.userID);
                            userData.putString("name", zegoUser.userName);
                            message.setData(userData);
                            mHandler.sendMessageDelayed(message, HandlerImpl.INTERVAL_WAIT_CONFIRM);
                        }
                    });

                    AppLogger.getInstance().writeLog("Server是否成功通知预约用户准备开始游戏? %s", success);
                }
            }
        });
    }

    // 在Work线程处理预约指令
    private void handleAppointmentCommandInWorkThread(final String fromUserId, final String fromUserName, final JSONObject cmdJson) {
        mRoomClient.runOnWorkThread(new Runnable() {
            @Override
            public void run() {
                handleAppointmentCommand(fromUserId, fromUserName, cmdJson);
            }
        });
    }

    /**
     * 处理预约指令
     */
    private void handleAppointmentCommand(String fromUserId, String fromUserName, JSONObject cmdJson) {
        ZegoLiveRoom liveRoom = mRoomClient.getZegoLiveRoom();

        final CustomZegoUser zegoUser = new CustomZegoUser();
        List<CustomZegoUser> queueMembers = mRoomClient.getQueueUser();
        boolean inQueue = false;  // 是否已经再队列中
        for (CustomZegoUser member : queueMembers) {
            if (TextUtils.equals(member.userID, fromUserId)) {
                inQueue = true;
                zegoUser.userID = member.userID;
                zegoUser.userName = member.userName;
                zegoUser.avatar = member.avatar;
                zegoUser.nickName = member.nickName;
                AppLogger.getInstance().writeLog("[handleAppointmentCommand]处理预约指令, old user: %s, queueMembers: %d", fromUserName, queueMembers.size());
                break;
            }
        }

        JSONObject dataObject = cmdJson.optJSONObject(JsonKey.KEY_DATA);

        if (!inQueue) {  // 预约用户未再队列中
            zegoUser.userID = fromUserId;
            zegoUser.userName = fromUserName;
            zegoUser.avatar = dataObject.optString(JsonKey.KEY_USER_AVATAR);
            zegoUser.nickName = dataObject.optString(JsonKey.KEY_USER_NICKNAME);
            queueMembers.add(zegoUser);

            // 收到预约申请，且是第一个用户，通知服务端改变房间状态
            if (queueMembers.size() == 1) {
                RequestMgr.reqRoomStatus(-1);
            }
            AppLogger.getInstance().writeLog("[handleAppointmentCommand]处理预约指令, new user: %s, queueMembers: %d", fromUserName, queueMembers.size());
        }

        CustomZegoUser[] userArray = {zegoUser};
        final String cmdString = SynthCmd.applyReplyCmd(zegoUser.userID, zegoUser.userName, queueMembers.size(), cmdJson);
        boolean success = liveRoom.sendCustomCommand(userArray, cmdString, new IZegoCustomCommandCallback() {
            @Override
            public void onSendCustomCommand(int errorCode, String roomId) {
                AppLogger.getInstance().writeLog("server回复预约指令状态码: %d， cmd: %s", errorCode, cmdString);
            }
        });
        AppLogger.getInstance().writeLog("server回复预约指令是否成功? %s", success);
        notifyNextPlayerIfNeed();

        if (!inQueue) {
            mListener.onRoomStateUpdate();
        }
    }

    /**
     * 处理取消预约指令
     */
    private void handleCancelAppointmentInWorkThread(final String userId, final String userName, final JSONObject cmdJson) {
        mRoomClient.runOnWorkThread(new Runnable() {
            @Override
            public void run() {
                handleCancelAppointment(userId, userName, cmdJson);
            }
        });
    }

    private void handleCancelAppointment(String userId, String userName, JSONObject cmdJson) {
        List<CustomZegoUser> queueMembers = mRoomClient.getQueueUser();
        CustomZegoUser zegoUser = new CustomZegoUser();
        int idx = -1;
        for (int i = 0; i < queueMembers.size(); i++) {
            if (TextUtils.equals(queueMembers.get(i).userID, userId)) {
                idx = i;
                zegoUser.userID = queueMembers.get(i).userID;
                zegoUser.userName = queueMembers.get(i).userName;
                zegoUser.avatar = queueMembers.get(i).avatar;
                zegoUser.nickName = queueMembers.get(i).nickName;
                break;
            }
        }

        if (idx >= 0) {
            queueMembers.remove(idx);
            AppLogger.getInstance().writeLog("[handleAppointmentCommand], remove user: %s, queueMembers: %d", userName, queueMembers.size());

            notifyNextPlayerIfNeed();
            mListener.onRoomStateUpdate();

            // 回复取消指令
            CustomZegoUser[] userArray = {zegoUser};
            String cmdString = SynthCmd.applyCancelCmd(userId, userName, 1, cmdJson);
            boolean success = mRoomClient.getZegoLiveRoom().sendCustomCommand(userArray, cmdString, null);
        } else {
            AppLogger.getInstance().writeLog("user %s not in queue, can't cancel apply", userName);
        }
    }

    /**
     * 在 Work 线程处理上机或者放弃游戏指令
     */
    private void handleStartOrAbandonCommandInWorkThread(final String userId, final String userName, final JSONObject cmdJson) {
        mRoomClient.runOnWorkThread(new Runnable() {
            @Override
            public void run() {
                handleStartOrAbandonCommand(userId, userName, cmdJson);
            }
        });
    }

    // 处理上机或者放弃游戏指令
    private void handleStartOrAbandonCommand(String userId, String userName, JSONObject cmdJson) {
        List<CustomZegoUser> queueMembers = mRoomClient.getQueueUser();

        if (queueMembers.size() == 0 || !TextUtils.equals(queueMembers.get(0).userID, userId) || !TextUtils.equals(queueMembers.get(0).userName, userName)) {
            AppLogger.getInstance().writeLog("用户: %s 未在预约队列中，忽略. queueMembers: %d", userName, queueMembers.size());
            return;
        }

        isReplayWait = false;
        mHandler.removeMessages(HandlerImpl.MSG_WAIT_REPLAY);   // 移除重玩等待计时
        mHandler.removeMessages(HandlerImpl.MSG_WAIT_CONFIRM);  // 移除上机等待计时

        ZegoLiveRoom liveRoom = mRoomClient.getZegoLiveRoom();

        // 获取队首上机用户，不移除
        CustomZegoUser user = queueMembers.get(0);
        AppLogger.getInstance().writeLog("[收到用户上机或放弃指令], 在预约等待队列中获取即将上机的用户信息: %s, queueMembers: %d", userName, queueMembers.size());

        CustomZegoUser[] sendTo = {user};
        final String replyCommand = SynthCmd.startOrAbandonReplyCmd(cmdJson);
        boolean success = liveRoom.sendCustomCommand(sendTo, replyCommand, new IZegoCustomCommandCallback() {
            @Override
            public void onSendCustomCommand(int errorCode, String roomId) {
                AppLogger.getInstance().writeLog("Server回复用户上机或放弃指令 Code: %d,  Cmd: %s", errorCode, replyCommand);
            }
        });
        AppLogger.getInstance().writeLog("Server是否成功回复用户上机或放弃指令 ? %s", success);

        int confirm = cmdJson.optJSONObject(JsonKey.KEY_DATA).optInt(JsonKey.KEY_CONFIRM);
        if (confirm == 1) { // 确认上机
            setIdle(false, "确认上机(confirm: 1)");
            mCurrentPlayerId = userId;
            mRoomClient.updateCurrentPlayerInfo(user.userID, user.userName);

            // 初始化设备
            JSONObject gameJson = cmdJson.optJSONObject(JsonKey.KEY_GAME_INIT);
            DeviceManager.getInstance().sendBeginCmd(gameJson);

            //计费，计时
            Message message = new Message();
            message.what = HandlerImpl.MSG_WAIT_GAME_OVER;
            message.obj = user.userID;
            mHandler.sendMessageDelayed(message, HandlerImpl.INTERVAL_GAME_TIME);
        } else {    // 放弃上机
            setIdle(true, "放弃上机(confirm: 0)");
            removeFromQueue(user.userID, user.userName);  // 移除队首用户
        }

        mListener.onRoomStateUpdate();
    }

    // 在 Work 线程处理上机应答指令
    private void handleReadyReplyCommandInWorkThread(final String userId, final String userName, final JSONObject jsonData) {
        mRoomClient.runOnWorkThread(new Runnable() {
            @Override
            public void run() {
                handleReadyReplyCommand(userId, userName, jsonData);
            }
        });
    }

    // 处理上机应答指令
    private void handleReadyReplyCommand(String userId, String userName, JSONObject jsonData) {

    }

    // 在 Work 线程处理游戏结果应答指令
    private void handleGameResultReplyCommandInWorkThread(final String userId, final String userName, final JSONObject jsoData) {
        mRoomClient.runOnWorkThread(new Runnable() {
            @Override
            public void run() {
                handleGameResultReplyCommand(userId, userName, jsoData);
            }
        });
    }

    // 处理游戏结果应答指令
    private void handleGameResultReplyCommand(final String userId, final String userName, final JSONObject jsoData) {

    }

    /**
     * 开始抓
     */
    private void doGrub(final String fromUserId, final String fromUserName) {

        // 发送"抓娃娃指令"成功后，设置超时定时器，避免获取不到下位机结果
        Bundle data = new Bundle();
        data.putString("id", fromUserId);
        data.putString("name", fromUserName);

        Message msg = Message.obtain();
        msg.what = HandlerImpl.MSG_WAIT_RECEIVE_DEVICE_RESULT;
        msg.obj = fromUserId;
        msg.setData(data);
        mHandler.sendMessageDelayed(msg, HandlerImpl.INTERVAL_WAIT_RECEIVE_DEVICE_RESULT);

        boolean success = DeviceManager.getInstance().sendDownCmd(new DeviceManager.OnGameOverObserver() {
            @Override
            public void onGameOver(boolean win) {
                if (mHandler.hasMessages(HandlerImpl.MSG_WAIT_RECEIVE_DEVICE_RESULT, fromUserId)) {   // 没有超时，正常返回给客户
                    mHandler.removeMessages(HandlerImpl.MSG_WAIT_RECEIVE_DEVICE_RESULT, fromUserId);
                    AppLogger.getInstance().writeLog("[HandlerImpl_doGrub] remove MSG_WAIT_RECEIVE_DEVICE_RESULT, userName: %s", fromUserName);

                    int result = win ? 1 : -1;
                    handleGameOverInWorkThread(result, fromUserId, fromUserName);
                } else {
                    AppLogger.getInstance().writeLog("[HandlerImpl_doGrub] no MSG_WAIT_RECEIVE_DEVICE_RESULT, userName: %s", fromUserName);
                }
            }
        });

        // 发送"抓娃娃指令"失败，直接抛出"抓取"失败
        if (!success) {
            AppLogger.getInstance().writeLog("[doGrub] sendDownCmd fail, userName: %s", fromUserName);
            handleGameOverInWorkThread(-1, fromUserId, fromUserName);
            return;
        }
    }

    /**
     * 为了响应速度，串口操作不进行线程切换
     */
    private void handleOperationCommand(int commandId, final String fromUserId, final String fromUserName) {
        if (mCurrentIsIdle || TextUtils.isEmpty(mCurrentPlayerId) || !TextUtils.equals(mCurrentPlayerId, fromUserId)) {
            AppLogger.getInstance().writeLog("anomaly operation, the playing user not the %s", fromUserName);
            return;
        }

        // send byte data to serial port
        switch (commandId) {
            case Command.CMD_MOVE_LEFT:
                DeviceManager.getInstance().sendLeftCmd();
                break;

            case Command.CMD_MOVE_RIGHT:
                DeviceManager.getInstance().sendRightCmd();
                break;

            case Command.CMD_MOVE_FORWARD:
                DeviceManager.getInstance().sendForwardCmd();
                break;

            case Command.CMD_MOVE_BACKWARD:
                DeviceManager.getInstance().sendBackwardCmd();
                break;

            case Command.CMD_GRAB:
                mHandler.removeMessages(HandlerImpl.MSG_WAIT_GAME_OVER);
                doGrub(fromUserId, fromUserName);
                break;
        }
    }

    /**
     * 游戏结束
     */
    private void handleGameOverInWorkThread(final int result, final String userId, final String userName) {
        mRoomClient.runOnWorkThread(new Runnable() {
            @Override
            public void run() {
                gameOver(result, userId, userName);
            }
        });
    }

    private void gameOver(int result, String userId, String userName) {
        RequestMgr.reqGameOver(result);

        ZegoLiveRoom liveRoom = mRoomClient.getZegoLiveRoom();

        List<CustomZegoUser> allMembers = mRoomClient.getTotalUser();
        CustomZegoUser[] userArray = new CustomZegoUser[allMembers.size()];
        allMembers.toArray(userArray);

        int seq = CommandSeqManager.getInstance().getAndIncreaseSequence();

        List<CustomZegoUser> queueMembers = mRoomClient.getQueueUser();
        // 发送给游戏者游戏结果
        final String cmdString = SynthCmd.gameOverCmd(queueMembers, result, userId, userName, seq);
        sendGameResultCmd(liveRoom, userArray, cmdString, userName);

        setIdle(true, "gameOver");
        mRoomClient.updateCurrentPlayerInfo("", "");

        // 游戏结束后不在直接通知下一位，而是等待当前玩家是否重来一次，开启等待计时
        startReplayWait(userId, userName);

        mListener.onRoomStateUpdate();
    }

    private boolean isReplayWait = false;  // 是否处于等待重玩
    private int resultTime = 0;  // 发送游戏结果次数

    private void sendGameResultCmd(final ZegoLiveRoom liveRoom, final CustomZegoUser[] userArray, final String cmdString, final String userName) {
        boolean success = liveRoom.sendCustomCommand(userArray, cmdString, new IZegoCustomCommandCallback() {
            @Override
            public void onSendCustomCommand(int errorCode, String roomId) {
                AppLogger.getInstance().writeLog("发送游戏结果CMD指令: %s", cmdString);
                if (errorCode != 0 && resultTime < 2 && isReplayWait) {
                    resultTime++;
                    sendGameResultCmd(liveRoom, userArray, cmdString, userName);
                } else {
                    resultTime = 0;
                }
            }
        });
        AppLogger.getInstance().writeLog("给用户 %s 发送游戏结果CMD： %s success? %s", userName, cmdString, success);
    }

    // 玩家重玩等待间隔
    private void startReplayWait(String userId, String userName) {
        Message message = new Message();
        message.what = HandlerImpl.MSG_WAIT_REPLAY;

        Bundle userData = new Bundle();
        userData.putString("id", userId);
        userData.putString("name", userName);
        message.setData(userData);
        isReplayWait = true;
        mHandler.sendMessageDelayed(message, HandlerImpl.INTERVAL_REPLAY_TIME);
    }

    /**
     * 预约队列中移除
     */
    private void removeFromQueue(final String userId, final String userName) {
        mRoomClient.runOnWorkThread(new Runnable() {
            @Override
            public void run() {
                int idx = -1;
                List<CustomZegoUser> queue = mRoomClient.getQueueUser();
                for (int i = 0; i < queue.size(); i++) {
                    CustomZegoUser user = queue.get(i);
                    if (TextUtils.equals(userId, user.userID) && TextUtils.equals(userName, user.userName)) {
                        idx = i;
                        break;
                    }
                }

                if (idx >= 0) {
                    queue.remove(idx);
                    AppLogger.getInstance().writeLog("[removeFromQueue], remove user: %s, queueMembers: %d", userName, queue.size());
                }

                setIdle(true, "removeFromQueue");
                notifyNextPlayerIfNeed();

                // 预约队列中无用户，通知服务器游戏结束, 更改房间状态
                if (queue != null && queue.size() <= 0) {
                    RequestMgr.reqRoomStatus(1);
                }
            }
        });
    }

    /**
     * 计时器
     */
    private class HandlerImpl implements Handler.Callback {
        static final public int MSG_WAIT_CONFIRM = 0x1;                   // 等待玩家确认上机/放弃指令
        static final public int INTERVAL_WAIT_CONFIRM = (10 + 2) * 1000;  // 玩家确认上机/放弃指令等待时间，等待12s(比客户端多2s)开始游戏，否则视为放弃

        static final public int MSG_WAIT_GAME_OVER = 0x2;               // 玩家确认上机后的游戏时间
        static final public int INTERVAL_GAME_TIME = (30 + 5) * 1000;   // 每局游戏最多 35s(比客户端多 5s，用来过滤重发信令所耗费的时间)，超时自动结束游戏

        // 雪暴与苏瑞娃娃机使用
        static final public int MSG_WAIT_RECEIVE_DEVICE_RESULT = 0x3;        // 下抓指令完成之后，等待下位机回馈
        static final public int INTERVAL_WAIT_RECEIVE_DEVICE_RESULT = 15000; // 等待下位机返回结果，不能超过客户端等待时间


        static final public int MSG_RESEND_READY_COMMAND = 0x4;
        static final public int MSG_RESEND_GAME_RESULT_COMMAND = 0x5;
        static final public int INTERVAL_RESEND_COMMAND_TIME = 1000;     // 重发指令间隔时间

        static final public int MSG_WAIT_REPLAY = 0x6;               // 玩家在玩一次等待时间
        static final public int INTERVAL_REPLAY_TIME = (5 + 2) * 1000;  // 最多等待 7s(比客户端多 2s，用来过滤重发信令所耗费的时间)，超时从队列移除用户


        @Override
        public boolean handleMessage(Message message) {
            switch (message.what) {
                case MSG_WAIT_CONFIRM: {// 未响应，认为放弃上机，通知队首玩家准备游戏
                    Bundle userData = message.getData();
                    String userId = userData.getString("id");
                    String userName = userData.getString("name");
                    AppLogger.getInstance().writeLog("上机用户 %s 未响应，通知对首玩家准备", userName);
                    removeFromQueue(userId, userName);
                }
                break;

                case MSG_WAIT_GAME_OVER: {
                    Bundle userData = message.getData();
                    final String userId = userData.getString("id");
                    final String userName = userData.getString("name");
                    AppLogger.getInstance().writeLog("timeout about MSG_WAIT_GAME_OVER, userName: %s", userName);
                    doGrub(userId, userName);
                }
                break;

                case MSG_WAIT_RECEIVE_DEVICE_RESULT: {
                    Bundle userData = message.getData();
                    String userId = userData.getString("id");
                    String userName = userData.getString("name");
                    AppLogger.getInstance().writeLog("下位机返回结果超时, userName: %s", userName);
                    handleGameOverInWorkThread(-1, userId, userName);    // 下位机返回超时，通知用户没有抓中
                }
                break;

                case MSG_WAIT_REPLAY:  // 重玩等待计时，超时后在预约队列移除
                    isReplayWait = false;
                    Bundle userData = message.getData();
                    String userId = userData.getString("id");
                    String userName = userData.getString("name");
                    AppLogger.getInstance().writeLog("上机用户 %s 未响应，通知队首玩家准备", userName);
                    removeFromQueue(userId, userName);
                    break;

                case MSG_RESEND_READY_COMMAND:
                    break;

                case MSG_RESEND_GAME_RESULT_COMMAND:
                    break;

                default:
                    return false;
            }
            return true;
        }
    }
}
